<?php
namespace app\adminapiv3\service;

/**
 * @name: netfilter 服务
 * @Param: 
 * @Description: 
 * @Author: foo
 * @Date: 2019-10-31 10:32:55
 */
use app\common\service;

class Netfilter extends Base
{
    private $processed = false;//是否已经处理过(用户检测规则冲突)
    private $jump_count = 1;
    private $chunk_count = 1;
    /**
     * @name: 配置修改
     * @Param: 
     * @Description: 
     * @Author: foo
     * @Date: 2019-10-31 10:33:24
     */
    function configModify ($host_id, $updateData)
    {
        $HostLearnConfig = new service\HostLearnConfig();
        if($updateData['now'])
        {
            $updateData['learn_status'] = 1;
        }

        $HostLearnConfig->toDo('modifyStudyConfig', [$host_id, $updateData]);

    }

    /**
     * @name: 设置详情
     * @Param: 
     * @Description: 
     * @Author: foo
     * @Date: 2019-10-31 10:33:41
     */
    function configInfo ($host_id)
    {
        $HostLearnConfig = new service\HostLearnConfig();
        $return = $HostLearnConfig->findOne($host_id);
        $nowtime = time();
        if($return['etime'] <= $nowtime)
        {
            $return['etime'] = $nowtime;
        }

        return $return;
    }

    /**
     * @name: 关闭学习
     * @Param: 
     * @Description: 
     * @Author: foo
     * @Date: 2019-10-31 11:11:18
     */
    function learnOff ($host_id)
    {
        $HostLearnConfig = new service\HostLearnConfig();//修改学习配置
        $HostLearnConfig->toDo('learnOff', [$host_id]);
    }

    /**
     * @name: 开启学习
     * @Param: 
     * @Description: 
     * @Author: foo
     * @Date: 2019-10-31 11:11:34
     */
    function learnOn($updateData)
    {
        $HostLink = new service\HostLink();
        $HostLink->toDo('checkOnline', ['netfilter']);

        $inited_host = $this->toDo('CheckInited');
        $HostLearnConfig = new service\HostLearnConfig();
        
        if (empty($updateData))
        {
            $HostLearnConfig->toDo('learnOn', [$inited_host]);//开始学习
        }
        else
        {
            $updateData['learn_status'] = 1;
            $HostLearnConfig->toDo('modifyStudyConfig', [$inited_host, $updateData]);
        }
    }

    /**
     * @name: 初始化
     * @Param: 
     * @Description: 
     * @Author: foo
     * @Date: 2019-10-31 14:32:02
     */
    function initialization ()
    {
        $host_id_a = $this->getHost('id');
        
        $NetfilterSafeRule = new service\NetfilterSafeRule();//修改主机安全规则状态
        $NetfilterSafeRule->toDo('init', [$host_id_a]);

        $HostLearnConfig = new service\HostLearnConfig();//修改主机学习设置
        $HostLearnConfig->toDo('initialization', [$host_id_a]);
    }

    /**
     * @name: 防火墙规则配置
     * @Param: 
     * @Description: 
     * @Author: foo
     * @Date: 2019-10-31 14:54:25
     */
    function firewallConfigModify ($host_id, $firewall)
    {
        $HostLearnConfig = new service\HostLearnConfig();//修改主机设置
        $HostLearnConfig->toDo('firewallConfigModify', [$host_id, $firewall]);
    }

    /**
     * @name: 原始规则列表
     * @Param: 
     * @Description: 
     * @Author: foo
     * @Date: 2019-10-31 15:42:02
     */
    function originRuleList ($host_id, $content, $limit)
    {
        $Netfilter = new service\HostInitialRule();
        $return = $Netfilter->toDo('list', [$host_id, $content, $limit]);

        return $return;
    }

    /**
     * @name: 原始规则删除
     * @Param: 
     * @Description: 
     * @Author: foo
     * @Date: 2019-10-31 15:42:30
     */
    function originRuleDelete ($host_id, $id)
    {
        $Netfilter = new service\HostInitialRule();
        $Netfilter->toDo('delete', [$host_id, $id]);
    }

    /**
     * @name: 学习数据列表
     * @Param: 
     * @Description: 
     * @Author: foo
     * @Date: 2019-11-01 16:24:15
     */
    function LearnDataStatisticsList ($filterData, $limit)
    {
        $HostLearnData = new service\HostLearnData();
        $return = $HostLearnData->toDo('groupList', [$filterData, $limit]);
        $return['data'] = $this->toDo('processStatisticsList', [$return['data'], $filterData['host_id']]);
        return $return;
    }

    /**
     * @name: 处理统计数据
     * @Param: 
     * @Description: 
     * @Author: foo
     * @Date: 2019-11-04 15:05:04
     */
    function processStatisticsList ($return, $host_id)
    {
        $mark = array_column ($return, 'mark');
        $HostLearnData = new service\HostLearnData();
        $outbount = $HostLearnData->toDo('statisticsList', [$host_id, $mark, 'outbount']);//统计出站
        $inbount = $HostLearnData->toDo('statisticsList', [$host_id, $mark, 'inbount']);//统计入站
        $all_view = $HostLearnData->toDo('statisticsList', [$host_id, $mark, 'all_view']);//总访问次数
        $have_rule = $HostLearnData->toDo('statisticsList', [$host_id, $mark, 'have_rule']);//已生成规则

        foreach ($mark as $k => $v)
        {
            $return[$k]['out'] = isset($outbount[$v]) ? $outbount[$v] : 0 ;
            $return[$k]['in'] = isset($inbount[$v]) ? $inbount[$v] : 0 ;
            $return[$k]['have_rule'] = isset($have_rule[$v]) ? $have_rule[$v] : 0 ;
            $return[$k]['all_view'] = isset($all_view[$v]) ? $all_view[$v] : 0 ;
        }

        return $return;
    }

    /**
     * @name: 学习数据列表
     * @Param: 
     * @Description: 
     * @Author: foo
     * @Date: 2019-11-01 16:24:15
     */
    function LearnDataList ($filterData, $limit)
    {
        $HostLearnData = new service\HostLearnData();
        $return = $HostLearnData->toDo('list', [$filterData, $limit]);

        foreach ($return['data'] as $k => $v){
            $return['data'][$k]['ctime'] = date('Y-m-d H:i:s', $v['ctime']);
        }
        return $return;
    }

    /**
     * @name: 学习数据删除
     * @Param: 
     * @Description: 
     * @Author: foo
     * @Date: 2019-11-04 14:14:40
     */
    function learnDataDelete ($host_id, $learn_id)
    {
        $HostLearnData = new service\HostLearnData();
        $return = $HostLearnData->toDo('selectByLearnId', [$host_id, $learn_id]);
        $HostLearnData->toDo('delete', [$host_id, $learn_id]);

        $HostRule = new service\HostRule();
        $HostRule->toDo('autoRuleDelete', [$host_id, $learn_id]);

        return $return;
    }

    /**
     * @name: 创建防火墙规则
     * @Param: 
     * @Description: 
     * @Author: foo
     * @Date: 2019-11-04 16:08:00
     */
    function createFirewallRule ($host_id, $learn_id)
    {
        $HostLearnData = new service\HostLearnData();
        $HostRule = new service\HostRule();
        $learnData = $HostLearnData->toDo('selectByLearnId', [$host_id, $learn_id]);
        $learn_id_a = $return = [];
        foreach ($learnData as $data)
        {
            $this->processed = false;
            $clientData = $HostLearnData->clientRule($data);
            $checked = $this->processInput($clientData, $HostRule);
            if($checked)
            {
                $learn_id_a[] = $data['id'];
                $return[] = $clientData;
            }
        }

        $HostLearnData->toDo('createRule', [$host_id, $learn_id_a]);

        $HostRule->toDo('morefirstRuleAddFid', [$host_id]);

        return $return;
    }

    /**
     * @name: 防火墙规则列表
     * @Param: 
     * @Description: 
     * @Author: foo
     * @Date: 2019-11-05 10:34:08
     */
    function firewallRuleList ($filterData, $limit)
    {
        $HostRule = new service\HostRule();
        $return = $HostRule->toDo('list', [$filterData, $limit]);
        $fid_a = array_column($return['data'], 'fid');
        $childData = $HostRule->toDo('childList',[$filterData['host_id'], $fid_a]);
        $return['data'] = $this->toDo('processOut',[$childData]);

        return $return;
    }

    /**
     * @name: 处理输出
     * @Param: 
     * @Description: 
     * @Author: foo
     * @Date: 2019-11-05 18:14:41
     */
    function processOut ($childData)
    {
        $return = [];
        if (empty($childData))
        {
            return $return;
        }
        $ruleKeys = ['id','host_id','direction','protocols','process','status','type','learn_id','have_mac_addr','mac_addr','fid','hit'];
        foreach ($childData as $rule)
        {
            empty($rule['alias']) ? : $return[$rule['fid']]['alias'] = $rule['alias'];
            foreach ($ruleKeys as $name)
            {
                isset($rule[$name]) ? $return[$rule['fid']][$name] = $rule[$name] : '';
            }
            $this->array2string('src_ip', $return, $rule);
            $this->array2string('dest_ip', $return, $rule);
            $this->array2string('src_port', $return, $rule, ':');
            $this->array2string('dest_port', $return, $rule, ':');
        }

        foreach ($return as $v)
        {
            $v['src_ip'] = implode(',', $v['src_ip']);
            $v['dest_ip'] = implode(',', $v['dest_ip']);
            $v['src_port'] = implode(',', $v['src_port']);
            $v['dest_port'] = implode(',', $v['dest_port']);
            $data[] = $v;
        }

        return $data;
    }

    /**
     * @name: 输出 数组转字符串
     * @Param: 
     * @Description: 
     * @Author: foo
     * @Date: 2019-11-05 18:21:02
     */
    private function array2string ($field, &$fData, $child, $childDelimiter = '-')
    {
        $field_s = $field . '_s';
        $field_e = $field . '_e';
        if(strchr($field, 'ip'))
        {
            if($child[$field_s] == 0 && $child[$field_e] == 4294967295)
            {
                $start_s = $start_e = 'any';
            }
            else
            {
                $start_s = long2ip((int) $child[$field_s]);
                $start_e = long2ip((int) $child[$field_e]);
            }
        }
        else
        {
            if($child[$field_s] == 0 && $child[$field_e] == 65535)
            {
                $start_s = $start_e = 'any';
            }
            else
            {
                $start_s = $child[$field_s];
                $start_e = $child[$field_e];
            }
        }
        
        $field_str = $start_s == $start_e ? $start_s : $start_s . $childDelimiter . $start_e;
        $fData[$child['fid']][$field][$field_str] = $field_str;

        return $fData;
    }

    /**
     * @name: 添加规则
     * @Param: 
     * @Description: 
     * @Author: foo
     * @Date: 2019-11-05 14:37:51
     */
    function firewallRuleAdd ($data)
    {
        $HostLink = new service\HostLink();
        $HostLink->toDo('checkOnline', ['netfilter']);

        $HostRule = new service\HostRule();
        $insertData = $this->toDo('processInput', [$data, $HostRule]);

        $this->toDo('add', [$insertData, $data['alias'], $HostRule]);

    }

        /**
     * @name: 添加
     * @Param: 
     * @Description: 
     * @Author: foo
     * @Date: 2019-11-20 11:23:24
     */
    function add ($insertData, $alias, $server)
    {
        $insertData = array_chunk($insertData, $this->chunk_count / $this->jump_count);
        foreach ($insertData as $v)
        {
            $f_insert_data = array_shift($v);
            $f_insert_data['alias'] = $alias;
            $fid = $server->firstAdd($f_insert_data);//父规则
            $child_a = array_map(function($cv) use ($fid){
                $cv['fid'] = $fid;
                return $cv;
            },$v);
            $server->childAdd($child_a);//子规则
            $server->firstRuleAddFid($fid, $f_insert_data['host_id']);
        }
    }

    /**
     * @name: 检测规则是否冲突
     * @Param: 
     * @Description: 
     * @Author: foo
     * @Date: 2019-11-14 17:53:03
     */
    private function checkRule ($data, $model, $fid)
    {
        $conflict_a = $return = [];
        $host_id_a = $this->CheckInited();
        if (empty($host_id_a))
        {
            return $return;
        }
        $data_count = count($data);
        foreach ($host_id_a as $host_id)
        {
            foreach ($data as $inputRule)
            {
                if($model->checkRule($host_id, $inputRule, $fid, $inputRule['protocols']) > 0)
                {
                    $conflict_a[] = $host_id;
                    break(1);
                }
            }
            
        }

        if(!empty($conflict_a))
        {

            $this->setError($this->CODE_FAIL, $this->hostErrorMessage($conflict_a, '规则冲突，请检查规则'));
            return $return;
        }
        $host_id_a = $this->getHost('id');
        $host_id_a = array_keys($host_id_a);
        $this->jump_count = count($host_id_a);

        foreach ($host_id_a as $k => $host_id)
        {
            for ($i=$k; $i<$data_count; $i+=$this->jump_count)
            {
                $data[$i]['host_id'] = $host_id;
                $return [] = $data[$i];
            }
        }

        return $return;
    }

    /**
     * @name: 处理输入端口
     * @Param: 
     * @Description: 
     * @Author: foo
     * @Date: 2019-11-05 14:59:22
     */
    function processInput ($data, $model)
    {

        if($this->processed)
        {
            return $data;
        }

        $return[] = [
            'protocols' => $data['protocols'],
            'direction' => $data['direction'],
            'type' => model('HostRule')->TYPE_HANDLE,
            'process' => $data['process'],
        ];

        $return = $this->processPortAndIp($data['src_ip'], 'src_ip', $return);
        $return = $this->processPortAndIp($data['dest_ip'], 'dest_ip', $return);
        $return = $this->processPortAndIp($data['src_port'], 'src_port', $return, ',', ':');
        $return = $this->processPortAndIp($data['dest_port'], 'dest_port', $return, ',', ':');

        $fid = isset($data['fid']) ? $data['fid'] : 0 ; 
        $return = $this->checkRule($return, $model, $fid);
        $this->processed = true;


        return $return;
    }

    /**
     * @name: 处理ip和端口
     * @Param: 
     * @Description: 
     * @Author: foo
     * @Date: 2019-11-15 14:08:50
     */
    private function processPortAndIp ($input, $field, $data, $delimiter = ',', $childDelimiter = '-')
    {
        if(stristr($input, $delimiter))
        {
            $input_a = explode($delimiter, $input);
            $inputData = array_map(function($v) use ($field, $childDelimiter){
               return $this->string2array($v, $field, $childDelimiter);
            }, $input_a);
        }
        else
        {
            $inputData[] = $this->string2array($input, $field, $childDelimiter);
        }
        $this->chunk_count *= count($inputData);

        foreach ($inputData as $v)
        {
            foreach ($data as $iv)
            {
                $return[] = array_merge($iv, $v);
            }
        }
        return $return;
    }
    /**
     * @name: 处理端口
     * @Param: 
     * @Description: 
     * @Author: foo
     * @Date: 2019-11-05 16:19:38
     */
    private function string2array ($data, $field, $delimiter)
    {
        
        $data_a = stristr($data, $delimiter) ? explode($delimiter, $data) : [$data, $data];
        if (stristr($field, 'ip'))
        {
            if ($data_a[0] == 'any' && $data_a[1] == 'any')
            {
                $return = [
                    $field . '_s' => 0,
                    $field . '_e' => 4294967295,
                ];
            }
            else
            {
                $return = [
                    $field . '_s' => sprintf('%u', ip2long($data_a[0])) ,
                    $field . '_e' => sprintf('%u', ip2long($data_a[1])) ,
                ];
            }
            
        }
        else
        {
            if ($data_a[0] == 'any' && $data_a[1] == 'any')
            {
                $return = [
                    $field . '_s' => 0,
                    $field . '_e' => 65535,
                ];
            }
            else
            {
                $return = [
                    $field . '_s' => $data_a[0],
                    $field . '_e' => $data_a[1],
                ];
            }
            
        }
        
        

        return $return;
    }

    /**
     * @name: 防火墙规则修改
     * @Param: 
     * @Description: 
     * @Author: foo
     * @Date: 2019-11-05 17:24:49
     */
    function firewallRuleModify ($updateData)
    {
        $HostRule = new service\HostRule();
               
        $list = $HostRule->childList($updateData['host_id'], $updateData['fid']);
        $return = $this->processOut($list);
        if (empty($return))
        {
           $this->setError($this->CODE_FAIL, '该条规则不存在或已被删除');
            return false;
        }
        $addData = $this->toDo('processInput', [$updateData, $HostRule]);//处理输入数据

        $HostRule->toDo('ruleDelete', [$updateData['host_id'], $updateData['fid']]);

        $this->toDo('add', [$addData, $updateData['alias'], $HostRule]);

        $HostLearnData = new service\HostLearnData();
        $HostLearnData->toDo('resetStatus', [$updateData['host_id'], $updateData['learn_id']]);
        return $return;
    }

    /**
     * @name: 规则删除
     * @Param: 
     * @Description: 
     * @Author: foo
     * @Date: 2019-11-05 17:32:13
     */
    function firewallRuleDelete ($host_id, $rule_id)
    {

        $HostRule = new service\HostRule();
        $list = $HostRule->childList($host_id, $rule_id);
        $return = $this->processOut($list);
        if (empty($return))
        {
            $this->setError($this->CODE_FAIL, '该条规则不存在或已被删除');
            return false;
        }
        $learnId_a = array_column($list, 'learn_id');
        $HostRule->toDo('ruleDelete', [$host_id, $rule_id]);
        $HostLearnData = new service\HostLearnData();
        $HostLearnData->toDo('resetStatus', [$host_id, $learnId_a]);

        return $return;
    }

    /**
     * @name: 启用规则
     * @Param: 
     * @Description: 
     * @Author: foo
     * @Date: 2019-11-12 13:52:58
     */
    function firewallRuleOn ($host_id, $rule_id)
    {
        $HostRule = new service\HostRule();
        $list = $HostRule->childList($host_id, $rule_id);
        $return = $this->processOut($list);

        $HostRule->toDo('onOff', [$host_id, $rule_id, model('HostRule')->STATUS_ON]);

        return $return;

    }

    /**
     * @name: 禁用规则
     * @Param: 
     * @Description: 
     * @Author: foo
     * @Date: 2019-11-12 13:52:58
     */
    function firewallRuleOff ($host_id, $rule_id)
    {
        $HostRule = new service\HostRule();
        $list = $HostRule->childList($host_id, $rule_id);
        $return = $this->processOut($list);
        $HostRule->toDo('onOff', [$host_id, $rule_id, model('HostRule')->STATUS_OFF]);

        return $return;

    }

    /**
     * @name: 虚拟规则列表
     * @Param: 
     * @Description: 
     * @Author: foo
     * @Date: 2019-11-20 10:22:59
     */
    function virtualRuleList ($filterData, $limit)
    {
        $VirtualRule = new service\VirtualRule();
        $return = $VirtualRule->toDo('list', [$filterData, $limit]);
        $fid_a = array_column($return['data'], 'fid');
        $childData = $VirtualRule->toDo('childList', [$filterData['host_id'], $fid_a]);
        $return['data'] = $this->toDo('processOut',[$childData]);
        return $return;
    }

        /**
     * @name: 添加虚拟规则
     * @Param: 
     * @Description: 
     * @Author: foo
     * @Date: 2019-11-05 14:37:51
     */
    function virtualRuleAdd ($data)
    {
        $VirtualRule = new service\VirtualRule();
        $insertData = $this->toDo('processInput',[$data, $VirtualRule]);

        $this->toDo('add', [$insertData, $data['alias'], $VirtualRule]);
    }

        /**
     * @name: 虚拟规则修改
     * @Param: 
     * @Description: 
     * @Author: foo
     * @Date: 2019-11-05 17:24:49
     */
    function virtualRuleModify ($updateData)
    {
        $VirtualRule = new service\VirtualRule();
        
        $addData = $this->toDo('processInput', [$updateData, $VirtualRule]);//处理输入数据

        $VirtualRule->toDo('delete', [$updateData['host_id'], $updateData['fid']]);

        $this->toDo('add', [$addData, $updateData['alias'], $VirtualRule]);
    }

    /**
     * @name: 规则删除
     * @Param: 
     * @Description: 
     * @Author: foo
     * @Date: 2019-11-05 17:32:13
     */
    function virtualRuleDelete ($host_id, $rule_id)
    {
        $VirtualRule = new service\VirtualRule();
        $VirtualRule->toDo('delete', [$host_id, $rule_id]);

    }

    /**
     * @name: 开启规则
     * @Param: 
     * @Description: 
     * @Author: foo
     * @Date: 2019-11-20 13:47:37
     */
    function virtualRuleOn($host_id, $rule_id)
    {
        $VirtualRule = new service\VirtualRule();
        $VirtualRule->toDo('onOff', [$host_id, $rule_id, $VirtualRule->STATUS_USED]);
    }

    /**
     * @name: 关闭规则
     * @Param: 
     * @Description: 
     * @Author: foo
     * @Date: 2019-11-20 13:47:37
     */
    function virtualRuleOff($host_id, $rule_id)
    {
        $VirtualRule = new service\VirtualRule();
        $VirtualRule->toDo('onOff', [$host_id, $rule_id, $VirtualRule->STATUS_UNUSED]);
    }

    /**
     * @name: 默认规则列表
     * @Param: 
     * @Description: 
     * @Author: foo
     * @Date: 2019-12-20 16:17:25
     */
    function safeRuleList ($host_id, $limit)
    {
        $NetfilterSafeRule = new service\NetfilterSafeRule();
        $return = $NetfilterSafeRule->toDo('list', [$host_id, $limit]);

        return $return;
    }

    /**
     * @name: 开启默认规则
     * @Param: 
     * @Description: 
     * @Author: foo
     * @Date: 2019-12-20 16:17:56
     */
    function safeRuleOn ($rule_id)
    {
        $host_id_a = $this->getHost('id');
        $NetfilterSafeRule = new service\NetfilterSafeRule();
        $NetfilterSafeRule->toDo('selectById', [$host_id_a, $rule_id, 0]);
        
        $NetfilterSafeRule->toDo('trunOnOff', [$host_id_a, $rule_id, 1]);

        $return = $NetfilterSafeRule->toDo('selectById', [$host_id_a, $rule_id, 1]);

        return $return;
    }

    /**
     * @name: 关闭默认规则
     * @Param: 
     * @Description: 
     * @Author: foo
     * @Date: 2019-12-20 16:43:41
     */
    function safeRuleOff ($rule_id)
    {
        $host_id_a = $this->getHost('id');
        $NetfilterSafeRule = new service\NetfilterSafeRule();
        $NetfilterSafeRule->toDo('selectById', [$host_id_a, $rule_id, 1]);
        
        $NetfilterSafeRule->toDo('trunOnOff', [$host_id_a, $rule_id, 0]);

        $return = $NetfilterSafeRule->toDo('selectById', [$host_id_a, $rule_id, 0]);
        return $return;
    }

    /**
     * @name: 检测是否初始化
     * @Param: 
     * @Description: 
     * @Author: foo
     * @Date: 2019-12-24 15:02:37
     */
    function CheckInited ()
    {
        $host_id = $this->getHost('id');
        $HostLearnConfig = new service\HostLearnConfig();
        $host = $HostLearnConfig->selectByHostId($host_id, 'host_id,init');
        $uninited_host = $return = [];
        foreach ($host as $v)
        {
            if($v['init'] == 1)
            {
                $return[] = $v['host_id'];
            }
            else
            {
                $uninited_host[] = $v['host_id'];
            }

        }

        if(!empty($uninited_host))
        {
            $this->setError($this->CODE_FAIL, $this->hostErrorMessage($uninited_host, '未初始化，请重新选择'));
            return [];
        }

        return $return;
    }

    /**
     * @name: 虚拟规则命中日志
     * @Param: 
     * @Description: 
     * @Author: foo
     * @Date: 2020-01-08 14:48:22
     */
    function virtualRuleHitLog ($filterData, $limit)
    {
        $HostLearnConfig = new service\HostLearnConfig();
        $HostLearnConfig->clearReming($filterData['host_id']);
        $NetfilterVirtualRuleHitLog = new service\NetfilterVirtualRuleHitLog();
        $return = $NetfilterVirtualRuleHitLog->list($filterData, $limit);

        return $return;
    }
}